﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using PacManBoardControl;
using PacManLib;

namespace PacManFramework
{
    /// <summary>
    /// Form that shows the Pac-Man board and playing options.
    /// </summary>
    public partial class PacManForm : Form
    {
        #region Public methods and properties

        /// <summary>
        /// Constructor
        /// </summary>
        public PacManForm()
        {
            InitializeComponent();
        }

        #endregion

        #region Private functions

        /// <summary>
        /// Called when the form is first loaded.
        /// </summary>
        private void Form1_Load(object sender, EventArgs e)
        {
            try
            {
                // We register for log events. We will show any messages that 
                // are logged in the log window...
                Log.LogEvent += onLogEvent;

                // We load any AIs from the current folder...
                AIManager aiManager = AIManager.getInstance();

                // We create Game and tournament objects...
                m_game = new Game();
                m_tournament = new Tournament();

                // We show the available AIs for you to choose when 
                // playing single games...
                cbPacManAI.Items.AddRange(aiManager.getPacManAINames());
                if (cbPacManAI.Items.Count > 0) cbPacManAI.SelectedIndex = 0;

                cbGhost12AI.Items.AddRange(aiManager.getGhostAINames());
                if (cbGhost12AI.Items.Count > 0) cbGhost12AI.SelectedIndex = 0;

                cbGhost34AI.Items.AddRange(aiManager.getGhostAINames());
                if (cbGhost34AI.Items.Count > 0) cbGhost34AI.SelectedIndex = 0;

                // We create the sound objects...
                m_pelletEatenSound1 = ctrlPacManBoard.createSound(Sound.Sample.PelletEaten1);
                m_pelletEatenSound2 = ctrlPacManBoard.createSound(Sound.Sample.PelletEaten2);
                m_pelletEatenSound = m_pelletEatenSound1;
                m_sirenSound = ctrlPacManBoard.createSound(Sound.Sample.Siren);
                m_ghostsScaredSound = ctrlPacManBoard.createSound(Sound.Sample.GhostsScared);
                m_ghostEatenSound = ctrlPacManBoard.createSound(Sound.Sample.GhostEaten);
                m_pacManKilledSound = ctrlPacManBoard.createSound(Sound.Sample.PacManEaten);
            }
            catch (Exception ex)
            {
                Log.log("Form1_Load: " + ex.Message);
            }
        }

        /// <summary>
        /// Called every time a turn is played in a tournament.
        /// </summary>
        private void onTournamentTurnPlayedEvent(object sender, Tournament.Args e)
        {
            // We only show the board every 20 turns...
            ctrlPacManBoard.Board = e.Board;
            m_count++;
            if (m_count % 20 == 0)
            {
                ctrlPacManBoard.show();
                Application.DoEvents();
            }
        }

        /// <summary>
        /// Called when a message has been logged. We show it
        /// in a text box.
        /// </summary>
        private void onLogEvent(object sender, Log.Args e)
        {
            lstLog.Items.Add(e.Message);
        }

        /// <summary>
        /// Called when one game is over in a tournament.
        /// We update the scores.
        /// </summary>
        private void onTournamentGameOver(object sender, Tournament.Args e)
        {
            // We clear out any existing scores from the
            // data-grid-view where they are shown...
            ctrlTournamentResults.DataSource = null;
            m_tournamentScores.Clear();

            // We get the current scores from the tournament object...
            foreach (Tournament.AIScore score in m_tournament.getScores())
            {
                m_tournamentScores.Add(score);
            }

            // We make sure that the data-grid-view is set up to show
            // the new scores...
            ctrlTournamentResults.DataSource = m_tournamentScores;
            if (ctrlTournamentResults.Columns.Count >= 5)
            {
                ctrlTournamentResults.Sort(ctrlTournamentResults.Columns[TR_TOTAL_SCORE_COLUMN], ListSortDirection.Descending);
                ctrlTournamentResults.Columns[TR_NAME_COLUMN].Width = TR_NAME_COLUMN_WIDTH;
                ctrlTournamentResults.Columns[TR_PACMAN_SCORE_COLUMN].Width = TR_PACMAN_SCORE_COLUMN_WIDTH;
                ctrlTournamentResults.Columns[TR_GHOST_SCORE_COLUMN].Width = TR_GHOST_SCORE_COLUMN_WIDTH;
                ctrlTournamentResults.Columns[TR_TOTAL_SCORE_COLUMN].Width = TR_TOTAL_SCORE_COLUMN_WIDTH;
                ctrlTournamentResults.Columns[TR_TIME_COLUMN].Width = TR_TIME_COLUMN_WIDTH;
                ctrlTournamentResults.Columns[TR_SCORE_PER_SECOND_COLUMN].Width = TR_SCORE_PER_SECOND_COLUMN_WIDTH;
                ctrlTournamentResults.Columns[TR_GAMES_PLAYED_COLUMN].Width = TR_GAMES_PLAYED_COLUMN_WIDTH;
            }
            Application.DoEvents();
        }

        /// <summary>
        /// Called when the Start Game button is pressed.
        /// This plays a single game using the players selected in combo boxes.
        /// </summary>
        private void cmdStartGame_Click(object sender, EventArgs e)
        {
            try
            {
                // If a game is currently being played, we ignore the 
                // Start button...
                if (m_gameOver == false)
                {
                    return;
                }

                // We create a new game...
                createNewGame();

                // And set the AIs to use...
                AIManager aiManager = AIManager.getInstance();
                string pacManAI = (string)cbPacManAI.SelectedItem;
                m_game.setPacManAI(aiManager.getNewPacManAIInstance(pacManAI));

                string ghost12AI = (string)cbGhost12AI.SelectedItem;
                m_game.setGhostAI(0, aiManager.getNewGhostAIInstance(ghost12AI));
                m_game.setGhostAI(1, aiManager.getNewGhostAIInstance(ghost12AI));

                string ghost34AI = (string)cbGhost34AI.SelectedItem;
                m_game.setGhostAI(2, aiManager.getNewGhostAIInstance(ghost34AI));
                m_game.setGhostAI(3, aiManager.getNewGhostAIInstance(ghost34AI));

                // We hide the Game Over label...
                lblGameOver.Visible = false;

                // We start the timer that manages the game...
                int intervalMS = Convert.ToInt32(txtTimerInterval.Text);
                m_gameOver = false;
                while (m_gameOver == false)
                {
                    // We play a turn and show the board...
                    m_game.playOneTurn();
                    ctrlPacManBoard.showGliding(intervalMS);

                    // We update the scores...
                    lblPacManScore.Text = m_game.getPacManScore().ToString();
                    lblPacManLives.Text = m_game.getPacManLivesRemaining().ToString();
                    lblGhost1Score.Text = m_game.getGhostScore(0).ToString();
                    lblGhost2Score.Text = m_game.getGhostScore(1).ToString();
                    lblGhost3Score.Text = m_game.getGhostScore(2).ToString();
                    lblGhost4Score.Text = m_game.getGhostScore(3).ToString();
                }
            }
            catch(Exception ex)
            {
                Log.log("cmdStartGame_Click: " + ex.Message);
            }
        }

        /// <summary>
        /// Creates a new game, when playing in single-game mode.
        /// </summary>
        private void createNewGame()
        {
            // We detach all events from the previous game...
            m_game.GameOverEvent -= onGameOverEvent;
            m_game.PelletEatenEvent -= onPelletEatenEvent;
            m_game.PowerPelletEatenEvent -= onPowerPelletEatenEvent;
            m_game.GhostsNoLongerScaredEvent -= onGhostsNoLongerScaredEvent;
            m_game.GhostKilledEvent -= onGhostKilledEvent;
            m_game.PacManKilledEvent -= onPacManKilledEvent;

            // We create a new game...
            m_game = new Game();
            ctrlPacManBoard.Board = m_game.Board;
            ctrlPacManBoard.show();

            // We attach events to the new game...
            m_game.GameOverEvent += onGameOverEvent;
            m_game.PelletEatenEvent += onPelletEatenEvent;
            m_game.PowerPelletEatenEvent += onPowerPelletEatenEvent;
            m_game.GhostsNoLongerScaredEvent += onGhostsNoLongerScaredEvent;
            m_game.GhostKilledEvent += onGhostKilledEvent;
            m_game.PacManKilledEvent += onPacManKilledEvent;

            // We play the siren sound...
            m_sirenSound.playLooped();
        }

        /// <summary>
        /// Called when a game is over, when playing a single game.
        /// </summary>
        private void onGameOverEvent(object sender, Game.Args e)
        {
            try
            {
                cleanupSingleGame();
            }
            catch (Exception ex)
            {
                Log.log(ex.Message);
            }
        }

        /// <summary>
        /// cleans up after a single-game has been played.
        /// </summary>
        private void cleanupSingleGame()
        {
            // We stop the timer, which stops further rounds being played,
            // and we show the game-over text...
            m_gameOver = true;
            lblGameOver.Visible = true;
            m_sirenSound.stop();
            m_ghostsScaredSound.stop();
        }

        /// <summary>
        /// Called when Pac-Man is killed.
        /// </summary>
        private void onPacManKilledEvent(object sender, Game.Args e)
        {
            m_pacManKilledSound.play();
        }

        /// <summary>
        /// Called when a ghost is eaten.
        /// </summary>
        private void onGhostKilledEvent(object sender, Game.Args e)
        {
            m_ghostEatenSound.play();
        }

        /// <summary>
        /// Called when all ghosts have gone back to being hunters.
        /// </summary>
        private void onGhostsNoLongerScaredEvent(object sender, Game.Args e)
        {
            m_ghostsScaredSound.stop();
            m_sirenSound.playLooped();
        }

        /// <summary>
        /// Called when a power-pellet has been eaten.
        /// </summary>
        private void onPowerPelletEatenEvent(object sender, Game.Args e)
        {
            m_ghostsScaredSound.playLooped();
            m_sirenSound.stop();
        }

        /// <summary>
        /// Called when a pellet is eaten when we're playing a single game.
        /// </summary>
        private void onPelletEatenEvent(object sender, Game.Args e)
        {
            // We play the two pellet-eaten sounds alternatively...
            m_pelletEatenSound.play();
            m_pelletEatenSound = (m_pelletEatenSound == m_pelletEatenSound1) ? m_pelletEatenSound2 : m_pelletEatenSound1;
        }

        /// <summary>
        /// Called when the Play Tournament button is pressed.
        /// </summary>
        private void cmdPlayTournament_Click(object sender, EventArgs e)
        {
            try
            {
                // We unregister events from the previous tournament...
                m_tournament.TurnPlayedEvent -= onTournamentTurnPlayedEvent;
                m_tournament.GameOverEvent -= onTournamentGameOver;

                // We create a new tournament...
                m_tournament = new Tournament();
                m_tournament.TurnPlayedEvent += onTournamentTurnPlayedEvent;
                m_tournament.GameOverEvent += onTournamentGameOver;

                // And play it...
                int rounds = Convert.ToInt32(txtTournamentRounds.Text);
                m_tournament.play(rounds);
            }
            catch (Exception ex)
            {
                Log.log("cmdPlayTournament_Click: " + ex.Message);
            }
        }

        /// <summary>
        /// Called when the form is about to close.
        /// </summary>
        private void PacManForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {
                cleanupSingleGame();
            }
            catch (Exception ex)
            {
                Log.log(ex.Message);
            }
        }

        /// <summary>
        /// Called when the Stop Game button is pressed.
        /// </summary>
        private void cmdStopGame_Click(object sender, EventArgs e)
        {
            try
            {
                cleanupSingleGame();
            }
            catch (Exception ex)
            {
                Log.log(ex.Message);
            }
        }

        #endregion

        #region Constants

        // Columns for tournament results...
        private const int TR_NAME_COLUMN = 0;
        private const int TR_PACMAN_SCORE_COLUMN = 1;
        private const int TR_GHOST_SCORE_COLUMN = 2;
        private const int TR_TOTAL_SCORE_COLUMN = 3;
        private const int TR_TIME_COLUMN = 4;
        private const int TR_SCORE_PER_SECOND_COLUMN = 5;
        private const int TR_GAMES_PLAYED_COLUMN = 6;

        // Column widths for tournament results...
        private const int TR_NAME_COLUMN_WIDTH = 260;
        private const int TR_PACMAN_SCORE_COLUMN_WIDTH = 75;
        private const int TR_GHOST_SCORE_COLUMN_WIDTH = 70;
        private const int TR_TOTAL_SCORE_COLUMN_WIDTH = 70;
        private const int TR_TIME_COLUMN_WIDTH = 70;
        private const int TR_SCORE_PER_SECOND_COLUMN_WIDTH = 70;
        private const int TR_GAMES_PLAYED_COLUMN_WIDTH = 70;

        #endregion

        #region Private data

        // Manages a single game...
        private Game m_game = null;

        // True when a single-game has finished...
        private bool m_gameOver = true;

        // Manages a tournament...
        private Tournament m_tournament = null;
        private int m_count = 0;

        // A collection of players' scores when playing a tournament...
        private SortableBindingList<Tournament.AIScore> m_tournamentScores = new SortableBindingList<Tournament.AIScore>();

        // Sounds...
        private Sound m_pelletEatenSound = null;
        private Sound m_pelletEatenSound1 = null;
        private Sound m_pelletEatenSound2 = null;
        private Sound m_sirenSound = null;
        private Sound m_ghostsScaredSound = null;
        private Sound m_ghostEatenSound = null;
        private Sound m_pacManKilledSound = null;

        #endregion

    }
}